// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CC_PLAYBACK_DISPLAY_ITEM_LIST_H_
#define CC_PLAYBACK_DISPLAY_ITEM_LIST_H_

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/trace_event/trace_event.h"
#include "cc/base/cc_export.h"
#include "cc/base/contiguous_container.h"
#include "cc/base/rtree.h"
#include "cc/playback/discardable_image_map.h"
#include "cc/playback/display_item.h"
#include "cc/playback/display_item_list_settings.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"

class SkCanvas;

namespace cc {
class ClientPictureCache;
class DisplayItem;

namespace proto {
    class DisplayItemList;
}

class CC_EXPORT DisplayItemList
    : public base::RefCountedThreadSafe<DisplayItemList> {
public:
    // Creates a display item list.
    static scoped_refptr<DisplayItemList> Create(
        const DisplayItemListSettings& settings);

    // Creates a DisplayItemList from a Protobuf.
    // TODO(dtrainor): Pass in a list of possible DisplayItems to reuse
    // (crbug.com/548434).
    static scoped_refptr<DisplayItemList> CreateFromProto(
        const proto::DisplayItemList& proto,
        ClientPictureCache* client_picture_cache,
        std::vector<uint32_t>* used_engine_picture_ids);

    // Creates a Protobuf representing the state of this DisplayItemList.
    void ToProtobuf(proto::DisplayItemList* proto);

    // TODO(trchen): Deprecated. Apply clip and scale on the canvas instead.
    void Raster(SkCanvas* canvas,
        SkPicture::AbortCallback* callback,
        const gfx::Rect& canvas_target_playback_rect,
        float contents_scale) const;

    void Raster(SkCanvas* canvas, SkPicture::AbortCallback* callback) const;

    // Because processing happens in these CreateAndAppend functions, all the set
    // up for the item should be done via the args, which is why the return type
    // needs to be const, to prevent set-after-processing mistakes.

    // Most paired begin item types default to an empty visual rect, which will
    // subsequently be grown as needed to encompass any contained items that draw
    // content, such as drawing or filter items.
    template <typename DisplayItemType, typename... Args>
    const DisplayItemType& CreateAndAppendPairedBeginItem(Args&&... args)
    {
        return CreateAndAppendPairedBeginItemWithVisualRect<DisplayItemType>(
            gfx::Rect(), std::forward<Args>(args)...);
    }

    // This method variant is exposed to allow filters to specify their visual
    // rect since they may draw content despite containing no drawing items.
    template <typename DisplayItemType, typename... Args>
    const DisplayItemType& CreateAndAppendPairedBeginItemWithVisualRect(
        const gfx::Rect& visual_rect,
        Args&&... args)
    {
        size_t item_index = inputs_.visual_rects.size();
        inputs_.visual_rects.push_back(visual_rect);
        inputs_.begin_item_indices.push_back(item_index);

        return AllocateAndConstruct<DisplayItemType>(std::forward<Args>(args)...);
    }

    template <typename DisplayItemType, typename... Args>
    const DisplayItemType& CreateAndAppendPairedEndItem(Args&&... args)
    {
        DCHECK(!inputs_.begin_item_indices.empty());
        size_t last_begin_index = inputs_.begin_item_indices.back();
        inputs_.begin_item_indices.pop_back();

        // Note that we are doing two separate things below:
        //
        // 1. Appending a new rect to the |visual_rects| list associated with
        //    the newly-being-added paired end item, with that visual rect
        //    having same bounds as its paired begin item, referenced via
        //    |last_begin_index|. The paired begin item may or may not be the
        //    current last visual rect in |visual_rects|, and its bounds has
        //    potentially been grown via calls to CreateAndAppendDrawingItem().
        //
        // 2. If there is still a containing paired begin item after closing the
        //    pair ended in this method call, growing that item's visual rect to
        //    incorporate the bounds of the now-finished pair.
        //
        // Thus we're carefully pushing and growing by the visual rect of the
        // paired begin item we're closing in this method call, which is not
        // necessarily the same as |visual_rects.back()|, and given that the
        // |visual_rects| list is mutated in step 1 before step 2, we also can't
        // shorten the reference via a |const auto| reference. We could make a
        // copy of the rect before list mutation, but that would incur copy
        // overhead.

        // Ending bounds match the starting bounds.
        inputs_.visual_rects.push_back(inputs_.visual_rects[last_begin_index]);

        // The block that ended needs to be included in the bounds of the enclosing
        // block.
        GrowCurrentBeginItemVisualRect(inputs_.visual_rects[last_begin_index]);

        return AllocateAndConstruct<DisplayItemType>(std::forward<Args>(args)...);
    }

    template <typename DisplayItemType, typename... Args>
    const DisplayItemType& CreateAndAppendDrawingItem(
        const gfx::Rect& visual_rect,
        Args&&... args)
    {
        inputs_.visual_rects.push_back(visual_rect);
        GrowCurrentBeginItemVisualRect(visual_rect);

        return AllocateAndConstruct<DisplayItemType>(std::forward<Args>(args)...);
    }

    // Called after all items are appended, to process the items and, if
    // applicable, create an internally cached SkPicture.
    void Finalize();

    void SetIsSuitableForGpuRasterization(bool is_suitable)
    {
        inputs_.all_items_are_suitable_for_gpu_rasterization = is_suitable;
    }
    bool IsSuitableForGpuRasterization() const;

    void SetImpliedColorSpace(const gfx::ColorSpace& implied_color_space)
    {
        inputs_.implied_color_space_specified = true;
        inputs_.implied_color_space = implied_color_space;
    }
    bool HasImpliedColorSpace() const
    {
        return inputs_.implied_color_space_specified;
    }
    const gfx::ColorSpace& GetImpliedColorSpace() const
    {
        return inputs_.implied_color_space;
    }

    int ApproximateOpCount() const;
    size_t ApproximateMemoryUsage() const;
    bool ShouldBeAnalyzedForSolidColor() const;

    std::unique_ptr<base::trace_event::ConvertableToTraceFormat> AsValue(
        bool include_items) const;

    void EmitTraceSnapshot() const;

    void GenerateDiscardableImagesMetadata();
    void GetDiscardableImagesInRect(const gfx::Rect& rect,
        float contents_scale,
        std::vector<DrawImage>* images);

    void SetRetainVisualRectsForTesting(bool retain)
    {
        retain_visual_rects_ = retain;
    }

    size_t size() const { return inputs_.items.size(); }

    gfx::Rect VisualRectForTesting(int index)
    {
        return inputs_.visual_rects[index];
    }

    ContiguousContainer<DisplayItem>::const_iterator begin() const
    {
        return inputs_.items.begin();
    }

    ContiguousContainer<DisplayItem>::const_iterator end() const
    {
        return inputs_.items.end();
    }

private:
    explicit DisplayItemList(
        const DisplayItemListSettings& display_list_settings);
    ~DisplayItemList();

    RTree rtree_;
    // For testing purposes only. Whether to keep visual rects across calls to
    // Finalize().
    bool retain_visual_rects_ = false;

    // If we're currently within a paired display item block, unions the
    // given visual rect with the begin display item's visual rect.
    void GrowCurrentBeginItemVisualRect(const gfx::Rect& visual_rect);

    template <typename DisplayItemType, typename... Args>
    const DisplayItemType& AllocateAndConstruct(Args&&... args)
    {
        auto* item = &inputs_.items.AllocateAndConstruct<DisplayItemType>(
            std::forward<Args>(args)...);
        approximate_op_count_ += item->ApproximateOpCount();
        return *item;
    }

    int approximate_op_count_ = 0;

    DiscardableImageMap image_map_;

    struct Inputs {
        explicit Inputs(const DisplayItemListSettings& settings);
        ~Inputs();

        ContiguousContainer<DisplayItem> items;
        // The visual rects associated with each of the display items in the
        // display item list. There is one rect per display item, and the
        // position in |visual_rects| matches the position of the item in
        // |items| . These rects are intentionally kept separate
        // because they are not needed while walking the |items| for raster.
        std::vector<gfx::Rect> visual_rects;
        std::vector<size_t> begin_item_indices;
        const DisplayItemListSettings settings;
        bool all_items_are_suitable_for_gpu_rasterization = true;
        bool implied_color_space_specified = false;
        gfx::ColorSpace implied_color_space;
    };

    Inputs inputs_;

    friend class base::RefCountedThreadSafe<DisplayItemList>;
    FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, ApproximateMemoryUsage);
    DISALLOW_COPY_AND_ASSIGN(DisplayItemList);
};

} // namespace cc

#endif // CC_PLAYBACK_DISPLAY_ITEM_LIST_H_
